package state.bluetooth;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;

import state.Control;
import state.util.SerialUtil;

public class BluetoothConnection extends Control {
	protected final int ERROR_SIGNAL = 0xFFFFFFFF;
	protected static final int HEART_BREATH_SIGNAL = 0xFFFFFFFE;
	protected final int SIGN_IN_SIGNAL = 0xFFFFFFFD;
	protected final int SIGN_OUT_SIGNAL = 0xFFFFFFFC;

	private StreamConnection streamConnection;
	private InputStream inputStream;
	private OutputStream outputStream;

	private String localName; // Name of local device for this connection
	private String remoteName; // Name of remote device for this connection
	private String remoteDeviceName;
	private String url; // urlStrings used for connecting, for server this is empty.
	private int storedIndex = -1;
	private boolean isSignIn = false;

	private long lastTimeRecive;
	private long lastTimeSend;
	private boolean isRunning = false;

	private ProtocolListener listener;
	private ServerListener serverListener;

	/**
	 * Use on server
	 * 
	 * @param con
	 * @param ln
	 * @param rn
	 * @throws IOException
	 */
	protected BluetoothConnection(StreamConnection con, String ln, String rn, ProtocolListener listener, ServerListener serverListener) throws IOException {
		this.serverListener = serverListener;
		this.listener = listener;
		localName = ln;
		remoteName = rn;
		url = "";
		streamConnection = con;
		openStreams();
	}

	/**
	 * Use on client
	 * 
	 * @param urlStrings
	 * @param ln
	 * @param rn
	 * @throws IOException
	 */
	protected BluetoothConnection(String urlStrings, String ln, String rn, ProtocolListener listener) throws IOException {
		this.listener = listener;
		localName = ln;
		remoteName = rn;
		url = urlStrings;
		connect();
	}

	public void setProtocolListener(ProtocolListener listener) {
		this.listener = listener;
	}

	public void setServerListener(ServerListener serverListener) {
		this.serverListener = serverListener;
	}

	public void perform() {
		lastTimeRecive = System.currentTimeMillis();
		isRunning = true;
		while (isRunning) {
			int commandSignal = recieveInt();
			if (isClosed()) {
				break;
			}
			
			switch (commandSignal) {
			case SIGN_IN_SIGNAL:
				isSignIn = true;
				if (serverListener != null) {
					try {
						serverListener.onClientSignIn(this);
					} catch (Throwable ex) {
						ex.printStackTrace();
					}
				}
				break;
			case SIGN_OUT_SIGNAL:
				isSignIn = false;
				if (serverListener != null) {
					try {
						serverListener.onClientSignOut(this);
					} catch (Throwable ex) {
						ex.printStackTrace();
					}
				}
				break;
			case ERROR_SIGNAL:
				break;
			case HEART_BREATH_SIGNAL:
				break;
			default:
				if (commandSignal > 0) {
					byte[] data = recieve(commandSignal);
					if (isClosed()) {
						break;
					}
					try {
						listener.onNewMessage(data, this);
					} catch (Throwable ex) {
						ex.printStackTrace();
					}
				} 
				break;
			}
		}
	}

	public long getLastTimeSend() {
		return lastTimeSend;
	}

	public long getLastTimeRecive() {
		return lastTimeRecive;
	}

	private void connect() throws IOException {
		streamConnection = (StreamConnection) Connector.open(url);
		openStreams();
	}

	private void openStreams() throws IOException {
		inputStream = streamConnection.openInputStream();
		outputStream = streamConnection.openOutputStream();
	}

	public boolean isSignIn() {
		return isSignIn;
	}

	public void signIn() {
		sendCommand(SIGN_IN_SIGNAL);
	}

	public void signOut() {
		sendCommand(SIGN_OUT_SIGNAL);
	}

	protected InputStream getInputStream() {
		return inputStream;
	}

	protected OutputStream getOutputStream() {
		return outputStream;
	}

	public String getLocalName() {
		return localName;
	}

	public String getRemoteName() {
		return remoteName;
	}

	protected void setRemoteName(String remoteName) {
		this.remoteName = remoteName;
	}

	public String getRemoteDeviceName() {
		return remoteDeviceName;
	}

	protected void setRemoteDeviceName(String remoteDeviceName) {
		this.remoteDeviceName = remoteDeviceName;
	}

	public int getStoredIndex() {
		return storedIndex;
	}

	public void setStoredIndex(int storedIndex) {
		this.storedIndex = storedIndex;
	}

	protected String recieveString() {
		try {
			return new String(recieve(recieveInt()), "UTF-8");
		} catch (UnsupportedEncodingException e) {
			return new String(recieve(recieveInt()));
		}
	}
	
	public int recieveInt() {
		byte[] bytes = new byte[4];
		recieve(bytes, 4);
		return SerialUtil.deserialToInt(bytes);
	}
	
	public byte[] recieve(int len) {
		byte[] bytes = new byte[len];
		recieve(bytes, len);
		return bytes;
	}
	
	public void recieve(byte[] bytes, int len) {
		try {
			int count = 0;
			int off = 0;
			while (len > 0) {
				count = inputStream.read(bytes, off, len);
				len -= count;
				off += count;
			}
			lastTimeRecive = System.currentTimeMillis();
		} catch (Throwable ex) {
			close();
		}
	}
	
	public void send(String str) {
		try {
			send(str.getBytes("UTF-8"));
		} catch (UnsupportedEncodingException e) {
			send(str.getBytes());
		}
	}
	
	public void send(int value) {
		send(SerialUtil.serialNumber(value));
	}
	
	public void send(byte[] bytes) {
		send(bytes, 0, bytes.length);
	}
	
	public void send(byte[] bytes, int off, int len) {
		try {
			outputStream.write(SerialUtil.serialNumber(len));
			outputStream.write(bytes, off, len);
			outputStream.flush();
			lastTimeSend = System.currentTimeMillis();
		} catch (Throwable e) {
			close();
		}
	}
	
	public void sendCommand(int command) {
		try {
			outputStream.write(SerialUtil.serialNumber(command), 0, 4);
			outputStream.flush();
			lastTimeSend = System.currentTimeMillis();
		} catch (Throwable e) {
			close();
		}
	}
	
	public boolean isClosed() {
		if (streamConnection == null) {
			return true;
		}
		return false;
	}
	
	public void close() {
		if (isClosed()) {
			return;
		}
		
		isRunning = false;
		isSignIn = false;
		try {
			if (inputStream != null) {
				inputStream.close();
				inputStream = null;
			}
		} catch (IOException e) {
		}
		try {
			if (outputStream != null) {
				outputStream.close();
				outputStream = null;
			}
		} catch (IOException e) {
		}
		try {
			if (streamConnection != null) {
				streamConnection.close();
				streamConnection = null;
			}
		} catch (IOException e) {
		}
		if (listener != null) {
			listener.onDisconnect(this);
			listener = null;
		}
		serverListener = null;
	}
}
